﻿using System;
using System.Collections.Generic;
using System.Reflection;

using BizElements.Core;
using PostSharp.Aspects;

using BizElements.BusinessLayer;

namespace BizElements.BusinessLayer.Aspects
{
    /// <summary>
    /// Provides details for attributes dealing with logging aspect.
    /// </summary>
    /// <remarks>Suitable for projects which use <b>BizElements</b> generated code.</remarks>
    [Serializable]
    public sealed class LogDetailProvider : ILogDetailProvider
    {
        /// <summary>
        /// Gets actor which is executing the transaction.
        /// </summary>
        /// <param name="eventArgs">Event arguments specifying which method is being executed and which are its arguments.</param>
        /// <returns><see cref="IActor"/> object or null if actor cannot be determined.</returns>
        public IActor GetActor(MethodExecutionArgs eventArgs)
        {
            return AopUtil.GetActor(eventArgs);
        }

        /// <summary>
        /// Gets type of the object/entity associated with the transaction. Typically table or class name.
        /// </summary>
        /// <param name="eventArgs">Event arguments specifying which method is being executed and which are its arguments.</param>
        /// <returns>Type of object.</returns>
        public string GetObjectType(MethodExecutionArgs eventArgs)
        {
            string tableName = AopUtil.GetTableName(eventArgs);
            string objectType = tableName ?? eventArgs.Method.DeclaringType.Name;
            return objectType;
        }

        /// <summary>
        /// Gets ID of the object/entity associated with the transaction.
        /// </summary>
        /// <param name="eventArgs">Event arguments specifying which method is being executed and which are its arguments.</param>
        /// <returns>ID tag or null if it cannot be determined.</returns>
        public object GetObjectId(MethodExecutionArgs eventArgs)
        {
            if (eventArgs.Instance == null)
                return null;

            IEntity entity = eventArgs.Instance as IEntity;
            object id = (entity != null) ? entity.GetPrimaryKeyValue() : null;
            return id;
        }

        /// <summary>
        /// Gets array of object's public properties to be recorded.
        /// </summary>
        /// <param name="eventArgs">Event arguments specifying which method is being executed and which are its arguments.</param>
        /// <returns>Property names which are to be logged; empty array if no properties are to be logged.</returns>
        public string[] GetLoggedProperties(MethodExecutionArgs eventArgs)
        {
            if (eventArgs.Instance == null)
                return new string[0];

            IEntity entity = eventArgs.Instance as IEntity;
            if (entity != null)
                return GetEntityProperties(entity);
            else
                return GetPrimitiveProperties(eventArgs.Instance);
        }

        private static string[] GetEntityProperties(IEntity entity)
        {
            List<string> validProperties = new List<string>();
            foreach (IDbColumn col in entity.Table.Columns)
            {
                bool nullsRaiseException = !col.IsNullable && col.DataType.IsValueType;
                bool isNotValid = nullsRaiseException && entity.IsNull(col);
                if (!isNotValid)
                    validProperties.Add(col.PropertyName);
            }

            return validProperties.ToArray();
        }

        private static string[] GetPrimitiveProperties(object obj)
        {
            List<string> publicPrimitiveProperties = new List<string>();
            PropertyInfo[] allProperties = obj.GetType().GetProperties();
            foreach (PropertyInfo prop in allProperties)
            {
                bool isGetter = prop.CanRead && !IsIndexer(prop);
                if (isGetter)
                {
                    bool isPrimitiveOrString = prop.PropertyType.IsPrimitive || (prop.PropertyType == typeof(string));
                    if (isPrimitiveOrString)
                        publicPrimitiveProperties.Add(prop.Name);
                }                
            }

            return publicPrimitiveProperties.ToArray();
        }

        private static bool IsIndexer(PropertyInfo property)
        {
            return (property.Name == "Item");
        }
    }
}
